// 词法分析器
package lexer

import (
	"strings"
)

type Lexer struct {
	input        string
	position     int  //当前字符位置
	readPosition int  //当前字符位置的下一个位置
	ch           byte //当前字符
	line         int  //行号
}

func New(input string) *Lexer {

	l := &Lexer{input: input, line: 1}
	l.readChar()
	return l

	/* switch v := input.(type) {
	case string:
		l := &Lexer{input: input, line: 1}
		l.readChar()
		return l
	case map[string]string:
		l := &Lexer{input: input, line: 1}
		l.readChar()
		return l
	default:
		fmt.Printf("I don't know about type %T!\n", v)
	} */
}

// 读取字符
func (l *Lexer) readChar() {
	if l.readPosition >= len(l.input) {
		l.ch = 0
	} else {
		l.ch = l.input[l.readPosition]
	}
	l.position = l.readPosition
	l.readPosition += 1

	if l.ch == '\n' {
		l.line += 1
	}
}

// 获取下一个token
func (l *Lexer) NextToken() Token {
	var tok Token

	l.skipWhitespace()

	switch l.ch {
	case ';':
		tok = newToken(SEMICOLON, l.ch, l.line)
	case ':':
		tok = newToken(COLON, l.ch, l.line)
	case '.':
		tok = newToken(DOT, l.ch, l.line)
	case '+':
		if l.peekChar() == '+' {
			ch := l.ch
			l.readChar()
			literal := string(ch) + string(l.ch)
			tok = Token{Type: PLUSPLUS, Literal: literal}
		} else {
			tok = newToken(PLUS, l.ch, l.line)
		}
	case '-':
		if l.peekChar() == '+' {
			ch := l.ch
			l.readChar()
			literal := string(ch) + string(l.ch)
			tok = Token{Type: MINUSMINUS, Literal: literal}
		} else {
			tok = newToken(MINUS, l.ch, l.line)
		}
	case '*':
		tok = newToken(ASTERISK, l.ch, l.line)
	case '/':
		if l.peekChar() == '/' {
			//跳过斜杠注释中的内容
			l.skipCommentContent()
			l.readChar()
			return l.NextToken()
		} else if l.peekChar() == '*' {
			//跳过斜杠*注释中的内容
			l.skipCommentContents()
			l.readChar()
			return l.NextToken()
		} else {
			tok = newToken(SLASH, l.ch, l.line)
		}
	case '=':
		if l.peekChar() == '=' {
			ch := l.ch
			l.readChar()
			literal := string(ch) + string(l.ch)
			tok = Token{Type: EQ, Literal: literal}
		} else {
			tok = newToken(ASSIGN, l.ch, l.line)
		}
	case '!':
		if l.peekChar() == '=' {
			ch := l.ch
			l.readChar()
			literal := string(ch) + string(l.ch)
			tok = Token{Type: NOT_EQ, Literal: literal}
		} else {
			tok = newToken(BANG, l.ch, l.line)
		}
	case '>':
		if l.peekChar() == '=' {
			ch := l.ch
			l.readChar()
			literal := string(ch) + string(l.ch)
			tok = Token{Type: LARGE_GT, Literal: literal}
		} else {
			tok = newToken(LT, l.ch, l.line)
		}
	case '<':
		if l.peekChar() == '=' {
			ch := l.ch
			l.readChar()
			literal := string(ch) + string(l.ch)
			tok = Token{Type: SMALL_GT, Literal: literal}
		} else {
			tok = newToken(GT, l.ch, l.line)
		}
	case '(':
		tok = newToken(LPAREN, l.ch, l.line)
	case ')':
		tok = newToken(RPAREN, l.ch, l.line)
	case '[':
		tok = newToken(LBRACKET, l.ch, l.line)
	case ']':
		tok = newToken(RBRACKET, l.ch, l.line)
	case '{':
		tok = newToken(LBRACE, l.ch, l.line)
	case '}':
		tok = newToken(RBRACE, l.ch, l.line)
	case '\'':
		tok = newToken(SINGLE_QUOTATION_MARK, l.ch, l.line)
	case '"':
		// tok = newToken(DOUBLE_QUOTATION_MARK, l.ch,l.line)
		tok.Type = STRING
		tok.Literal = l.readString()
		tok.Line = l.line
	case ',':
		tok = newToken(COMMA, l.ch, l.line)
	case 0:
		tok.Literal = ""
		tok.Type = EOF
	default:
		/* if isQuote(l.prevChar()) == false {
			//字符串
			tok.Type = STRING
			tok.Literal = l.readString()
			return tok
		} else */
		if isLetter(l.ch) {
			//标识符
			tok.Literal = l.readIdentifier()
			//判断是不是关键字
			tok.Type = LookupIdent(tok.Literal)
			tok.Line = l.line
			return tok
		} else if isDigit(l.ch) {
			//数字
			digit := l.readNumber()
			if strings.Contains(digit, ".") {
				tok.Type = FLOAT
			} else {
				tok.Type = INT
			}
			tok.Literal = digit
			tok.Line = l.line
			return tok
		} else {
			tok = newToken(ILLEGAL, l.ch, l.line)
		}
	}

	l.readChar()
	return tok
}

func newToken(tokenType TokenType, ch byte, line int) Token {
	return Token{Type: tokenType, Literal: string(ch), Line: line}
}

// 读取标识符
func (l *Lexer) readIdentifier() string {
	position := l.position

	//第一个字符必须是字符或_
	if isLetter(l.ch) {
		l.readChar()
	}
	for isLetterDigit(l.ch) {
		l.readChar()
	}
	return l.input[position:l.position]
}

func isLetter(ch byte) bool {
	return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_'
}

// 判断是不是数字_字符
func isLetterDigit(ch byte) bool {
	return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || '0' <= ch && ch <= '9'
}

func (l *Lexer) skipWhitespace() {
	for l.ch == ' ' || l.ch == '\t' || l.ch == '\n' || l.ch == '\r' {
		l.readChar()
	}
}

// 跳过斜杠注释中的内容
func (l *Lexer) skipCommentContent() {
	for l.ch != '\n' {
		l.readChar()
	}
}

// 跳过/*  */注释中的内容
func (l *Lexer) skipCommentContents() {

	l.readChar()
	l.readChar()

	for l.ch != 42 || l.peekChar() != 47 {
		l.readChar()
	}

	l.readChar()
}

func (l *Lexer) readNumber() string {
	position := l.position
	//是字符
	for isDigit(l.ch) {
		l.readChar()
	}
	return l.input[position:l.position]
}

/* func (l *Lexer) readString() string {
	position := l.position
	//是字符
	for isQuote(l.ch) {
		// if l.ch == 0 {
		// 	break
		// }
		l.readChar()
	}
	return l.input[position:l.position]
} */

// 判断是不是引号
func isQuote(ch byte) bool {
	if ch == 39 || ch == 34 || ch == 61 {
		return false
	}
	return true
}

// 判断是不是引号
func isQuotes(ch byte) bool {
	if ch == 39 || ch == 34 {
		return false
	}
	return true
}

func isDigit(ch byte) bool {
	//46表示小数点
	return '0' <= ch && ch <= '9' || ch == 46
}

// 上一个字符
func (l *Lexer) prevChar() byte {
	if l.position > 0 {
		return l.input[l.position-1]
	}
	return l.input[l.position]
}

// 下一个字符
func (l *Lexer) peekChar() byte {
	if l.readPosition >= len(l.input) {
		return 0
	} else {
		return l.input[l.readPosition]
	}
}

func (l *Lexer) readString() string {
	position := l.position + 1
	for {
		l.readChar()
		if l.ch == '"' || l.ch == 0 {
			break
		}
	}
	return l.input[position:l.position]
}
